package probe.sound.core;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;

import probe.sound.code.Util;

public class SynPlay implements Syn {
  
  private Producer producer;
  private double timeDelay;
  @Override
  public void setProducer(Producer producer) {
    this.producer = producer;
  }
  
  @Override
  public void setTimeDelay(double timeDelay) {
    this.timeDelay = timeDelay;
  }
  
  @Override
  public void run() {
    try {
      runInner();
    } catch (Exception e) {
      if (e instanceof RuntimeException) throw (RuntimeException)e;
      throw new RuntimeException( e );
    }
  }
  
  public void runInner() throws Exception {
    AudioFormat b = new AudioFormat( (float)SAMPLE_RATE, 16, 2, true, false );
    
    SourceDataLine.Info info = new DataLine.Info( SourceDataLine.class, b, 1024 );
    
    SourceDataLine line = (SourceDataLine)AudioSystem.getLine( info );
    
    line.open();
    line.start();
    
    byte[] buf = new byte[line.getBufferSize()];
    
    int iCount = buf.length / 4;
    
    final Value value = new Value();
    
    double timeDelay = this.timeDelay;
    
    U: for (int u = 0; true; u++) {
      int bufferFilledOn = buf.length;
      I: for (int i = 0; i < iCount; i++) {
        
        long iSample = (long)u * (long)iCount + (long)i;
        
        double t = ((double)iSample) / SAMPLE_RATE;
        
        if (t > timeDelay) {
          bufferFilledOn = i * 4;
          break I;
        }
        
        value.clean();
        
        producer.produce( value, t );
        
        int left = Util.toSignedWord( value.left );
        int right = Util.toSignedWord( value.right );
        
        Util.writeWord( buf, 4 * i + 0, true, left );
        Util.writeWord( buf, 4 * i + 2, true, right );
      }
      
      if (bufferFilledOn == 0) break U;
      
      int offset = 0;
      while (offset < bufferFilledOn) {
        int toWrite = line.available();
        if (offset + toWrite > bufferFilledOn) {
          toWrite = bufferFilledOn - offset;
          if (toWrite <= 0) break;
        }
        offset += line.write( buf, offset, toWrite );
      }
    }
    
    line.drain();
    line.stop();
    line.close();
  }
  
}
